package daybook.model.transaction;

import daybook.annotation.Bean;
import daybook.model.Commodity;
import daybook.model.DC;
import daybook.util.Utils;
import org.immutables.value.Value;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

@Bean
@Value.Immutable
public abstract class InventoryEntry {
    public abstract Transaction getTransaction();
    public abstract TransactionEntry getTransactionEntry();

    // 库存余额（金钱）
    @Value.Default
    public BigDecimal getBalance() {
        return BigDecimal.ZERO;
    }

    // 库存余额（数量）
    @Value.Default
    public BigDecimal getCountBalance() {
        return BigDecimal.ZERO;
    }

    // cost to count map
    // 库存余额（成本，该成本买入的库存余额）
    @Value.Default
    public Map<BigDecimal, BigDecimal> getCostBalance() {
        return Collections.emptyMap();
    }

    // 明细
    public Set<Item> getItems() {
        return getTransactionEntry().getItems();
    }

    public static InventoryEntry of(Transaction transaction, TransactionEntry transactionEntry, BigDecimal balance, BigDecimal countBalance, Map<BigDecimal, BigDecimal> costBalance) {
        final ImmutableInventoryEntry.Builder builder = ImmutableInventoryEntry.builder()
                .transaction(transaction)
                .transactionEntry(transactionEntry)
                .balance(balance.add(transactionEntry.getAmount()));

        if (transactionEntry.getItems().isEmpty()) {
            builder.countBalance(countBalance)
                    .costBalance(costBalance);
        } else {
            final int dc = transactionEntry.getDc();
            final Map<BigDecimal, BigDecimal> costBalance2 = transactionEntry.getItems().stream()
                    .collect(TransactionSummary.costBalanceCollector(dc));
            Utils.mergeInto(costBalance2, costBalance, BigDecimal::add);

            final BigDecimal countBalance2Abs = transactionEntry.getItems().stream()
                    .collect(TransactionSummary.amountCollector(Item::getCount));
            builder.countBalance(countBalance.add(dc == DC.CREDIT ? countBalance2Abs.negate() : countBalance2Abs))
                    .costBalance(costBalance2);
        }
        return builder.build();
    }

    public LocalDate getDate() {
        return getTransaction().getDate();
    }

    public BigDecimal getAmount() {
        return getTransactionEntry().getAmount();
    }

    public Commodity getCommodity(Commodity defaultCommodity) {
        return getTransactionEntry().getCommodity(defaultCommodity);
    }

    public Optional<BigDecimal> getAverageCost() {
        if (getCountBalance().signum() == 0) {
            return Optional.empty();
        } else {
            return Optional.of(getBalance().divide(getCountBalance(), getBalance().scale(), RoundingMode.HALF_EVEN));
        }
    }
}
